package com.xiexie.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.wf.captcha.ArithmeticCaptcha;
import com.xiexie.seckill.config.AccessLimit;
import com.xiexie.seckill.exception.GlobalException;
import com.xiexie.seckill.pojo.SeckillMessage;
import com.xiexie.seckill.pojo.SeckillOrder;
import com.xiexie.seckill.pojo.User;
import com.xiexie.seckill.rabbitMQ.RabbitMQSender;
import com.xiexie.seckill.service.IGoodsService;
import com.xiexie.seckill.service.IOrderService;
import com.xiexie.seckill.service.ISeckillOrderService;
import com.xiexie.seckill.vo.GoodsVo;
import com.xiexie.seckill.vo.RespBean;
import com.xiexie.seckill.vo.RespBeanEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀接口
 */
@Slf4j
@Controller
@RequestMapping("seckill/")
public class SeckillController implements InitializingBean {

    @Autowired
    private IGoodsService goodsService;

    @Autowired
    private ISeckillOrderService seckillOrderService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitMQSender rabbitMQSender;

    @Autowired
    private RedisScript<Long> redisScript;

    // 使用内存来处理是否下单成功
    private Map<Long, Boolean> emptyStockMap = new HashMap<>();

    /**
     * 秒杀商品
     * @param model
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping("{path}/doSeckill")
    public String doSeckill(
            Model model, User user, Long goodsId, @PathVariable("path") String path) {
        if (null == user) {
            return "login";
        }

        // ------------redis预减库存操作--------------
        ValueOperations valueOperations = redisTemplate.opsForValue();

        // 判断是否存在path
        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return "错误的秒杀地址";
        }

        // 判断是否重复抢购
        String key = "order:" + user.getId() + ":" + goodsId;
        SeckillOrder seckillOrder = (SeckillOrder)redisTemplate.opsForValue().get(key);
        if (null != seckillOrder) {
            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "seckillFail";
        }

        // 内存标记的方法 减少redis的访问
        if (emptyStockMap.get(goodsId)) {
            return "没有库存了";
        }

        // redis原子性 递减操作
        //Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
        // 换成lua脚本执行
        Long stock =
                (Long) redisTemplate.execute(
                        redisScript,
                        Collections.singletonList("seckillGoods:" + goodsId),
                        Collections.emptyList());
        if (stock < 0) {

            emptyStockMap.put(goodsId, true);

            // redis原子性 递加操作  保存redis中库存没有了 是0的数值状态
            valueOperations.increment("seckillGoods:" + goodsId);
            return "库存没了";
        }

        // 下单
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        rabbitMQSender.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));

        // 返回正在等待中   需要页面做轮询处理 看是否下单成功

        //model.addAttribute("user", user);
        //GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
        //// 判断库存
        //if (goodsVo.getStockCount() < 1) {
        //    model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMessage());
        //    return "seckillFail";
        //}
        //// 判断是否重复抢购  在缓存中获取
        ////SeckillOrder seckillOrder = seckillOrderService.getOne(
        ////        new QueryWrapper<SeckillOrder>()
        ////                .eq("user_id", user.getId())
        ////                .eq("goods_id", goodsId)
        ////);
        //String key = "order:" + user.getId() + ":" + goodsVo.getId();
        //SeckillOrder seckillOrder = (SeckillOrder)redisTemplate.opsForValue().get(key);
        //if (null != seckillOrder) {
        //    model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
        //    return "seckillFail";
        //}
        //
        //Order order = orderService.seckill(user, goodsVo);
        //model.addAttribute("order", order);
        //model.addAttribute("goods", goodsVo);
        return "orderDetail";
    }

    /**
     * 获取秒杀结果
     *
     * @param user
     * @param goodsId
     * @return  orderId：成功   -1：秒杀失败   0：排队中
     */
    @RequestMapping(value = "result", method = RequestMethod.GET)
    @ResponseBody
    public RespBean getResult(User user, Long goodsId) {
        if (null == user) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }
        Long orderId = seckillOrderService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }

    /**
     * 秒杀接口优化，获取真实的地址后在请求秒杀，防止被机刷真实地址造成压力
     * 验证 验证码
     *
     * @param user
     * @param goodsId
     * @return
     */
    @AccessLimit(second = 5, maxCount = 5, needLogin = true)
    @RequestMapping(value = "path", method = RequestMethod.GET)
    @ResponseBody
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {

        if (null == user) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }

        // 以下方法 放在注解中
        // 限制接口访问次数  （5秒中之内最多访问5次）
        // 缺点 1、没办法智能切换次数  2、在空档期造成资源浪费
        //ValueOperations valueOperations = redisTemplate.opsForValue();
        //String uri = request.getRequestURI();
        //String key = uri + ":" + user.getId();
        //Integer count = (Integer) valueOperations.get(key);
        //if (count == null) {
        //    valueOperations.set(key, 1, 5, TimeUnit.SECONDS);
        //} else if (count < 5) {
        //    valueOperations.increment(key);
        //} else {
        //    return RespBean.error("访问过于频繁，请稍后访问");
        //}

        // 验证 captcha
        boolean isCheck = orderService.checkCaptcha(user, goodsId, captcha);
        if (!isCheck) {
            return RespBean.error("验证码错误");
        }
        String path = orderService.createPath(user, goodsId);
        return RespBean.success(path);
    }

    @RequestMapping(value = "captcha", method = RequestMethod.GET)
    public void verifycode(User user, Long goodsId, HttpServletResponse response) {
        if (null == user) {
            throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
        }
        // 设置请求头为输出图片的类型
        response.setContentType("image/jpg");
        // 不需要缓存
        response.setHeader("Pargam", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        // 生成算数验证码 并放在redis中
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        String key = "captcha:" + user.getId() + ":" + goodsId;
        redisTemplate.opsForValue().set(key, captcha.text(), 300, TimeUnit.SECONDS);
        try {
            captcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败", e.getMessage());
        }
    }


    /**
     * 系统初始化的时候会调用   加载商品库存数量到redis中
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            emptyStockMap.put(goodsVo.getId(), false);
        });
    }
}
